/*
 * Copyright (C) 2001-2005 the xine-project
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 *
 * $Id: utils.c,v 1.74 2006/04/09 14:40:12 dsalt Exp $
 *
 * needful things
 */

#include "globals.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>

#include <glib.h>
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#include <gtk/gtk.h>

#include "ui.h"
#include "utils.h"
#include "engine.h"
#include "http.h"
#include "player.h"
#include "playlist.h"
#include "gtkvideo.h"

static int ensure_mkdir (const char *path, mode_t mode)
{
  struct stat st;
  if (stat (path, &st))
    return errno != ENOENT ? errno : (mkdir (path, mode) ? errno : 0);
  if (!S_ISDIR (st.st_mode))
    return mkdir (path, mode) ? errno : 0; /* should be an error */
  return 0;
}

int ensure_path_exists (char *path, int mode)
{
  char *sep, *start;

  start = path + (*path == '/');
  while  ((sep = strchr (start, '/')))
  {
    int ret;
    *sep = 0;
    ret = ensure_mkdir (path, mode);
    *sep = '/';
    if (ret)
      return ret;
    start = sep + 1;
  }

  return ensure_mkdir (path, mode); /* leave mode as is if already present */
}

char *make_path_uri (const char *path)
{
  if (g_path_is_absolute (path))
    return g_filename_to_uri (path, NULL, NULL);

  if ((*path & 0xDF) < 'A' || (*path & 0xDF) > 'Z')
    goto make_abs;

  const char *tmp = path + 1;
  for (; *tmp; ++tmp)
    if ((*tmp != '-' && *tmp < '0') || (*tmp > '9' && *tmp < 'A') ||
	(*tmp > 'Z' && *tmp < 'a') || *tmp > 'z')
      break;
  if (tmp[0] == ':' && tmp[1] == '/')
    return NULL; /* is a URI */

  make_abs:; /* is a relative path - make it absolute then a URI */
  char *dir = g_get_current_dir ();
  char *abspath = g_build_path (G_DIR_SEPARATOR_S, dir, path, NULL);
  free (dir);
  dir = g_filename_to_uri (abspath, NULL, NULL);
  free (abspath);
  return dir;
}

char *read_entire_file (const char *mrl, ssize_t *file_size)
{
  char        *buf;
  struct stat  statb;
  int          fd;
  ssize_t      size;

  if (!strncasecmp (mrl, "http://", 7))
    return http_download (mrl, file_size);

  if (file_size)
    *file_size = 0;

  if (stat (mrl, &statb) < 0)
  {
    /*g_print (_("utils: cannot stat '%s'\n"), mrl); */
    return NULL;
  }

  if (S_ISDIR (statb.st_mode))
  {
    errno = EISDIR;
    return NULL;
  }

  size = statb.st_size;

  fd = open (mrl, O_RDONLY);
  if (fd<0)
    return NULL;

  /* two extra bytes to ensure LF+NUL termination */
  buf = malloc (size + 2);
  if (!buf)
  {
    close (fd);
    errno = ENOMEM;
    return NULL;
  }

  size = read (fd, buf, size);
  if (size < 0)
  {
    /* read error */
    free (buf);
    size = errno;
    close (fd);
    errno = size;
    return NULL;
  }

  if (file_size)
    *file_size = size;

  if (size && buf[size - 1] != '\n')
    buf[size++] = '\n';
  buf[size] = 0;

  close (fd);

  return buf;
}

char *get_config_filename (const char *leaf)
{
  return g_build_filename (g_get_home_dir (), ".gxine", leaf, NULL);
}

FILE *open_write (const char *file, const char *errtitle)
{
  char *fname = g_strconcat (file, ".new", NULL);
  FILE *fd = NULL;
  if ((unlink (fname) && errno != ENOENT) || !(fd = fopen (fname, "w")))
    display_error_modal (FROM_GXINE, errtitle, _("Can't open file ‘%s’: %s"),
			 fname, strerror (errno));
  free (fname);
  return fd;
}

void close_write (const char *file, FILE *fd, const char *errtitle)
{
  char *fname = g_strconcat (file, ".new", NULL);
  int error;

  if ((error = ferror (fd)))
    display_error_modal (FROM_GXINE, errtitle,
			 _("Error while writing to ‘%s’: %s"),
			 fname, strerror (errno));
  if (fclose (fd))
  {
    display_error_modal (FROM_GXINE, errtitle,
			 _("Error when closing ‘%s’: %s"),
			 fname, strerror (errno));
    error = 1;
  }
  if (!error && rename (fname, file))
    display_error_modal (FROM_GXINE, errtitle,
			 _("Error when replacing ‘%s’: %s"),
			 file, strerror (errno));
}

void window_show (GtkWidget *const widget, GtkWidget *const parent)
{
  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_WINDOW (widget));
  g_return_if_fail (parent == NULL || GTK_IS_WINDOW (parent));
  gtk_window_set_transient_for ((GtkWindow *)widget,
				(GtkWindow *)(parent ? : app));
  gtk_widget_show_all (widget);
  gdk_window_raise (widget->window);
}

static void response_cb (GtkWidget *dialog, gint response, gpointer data)
{
  free (g_object_get_data (G_OBJECT (dialog), "msg"));
  if (!data)
    gtk_widget_destroy (dialog);
}

void v_display_message (gxine_msg_source src, gboolean modal,
			const gchar *primary, GtkMessageType type,
			const gchar *fmt, va_list ap)
{
  static const char *const wtitle[][4] = {
    { N_("Message from gxine"),
      N_("Warning from gxine"),
      N_("Question from gxine"),
      N_("Error from gxine"),
    },
    { N_("Message from the xine engine"),
      N_("Warning from the xine engine"),
      N_("Question from the xine engine"),
      N_("Error from the xine engine"),
    },
  };
  static const char *const ptitle[] = { "gxine", "xine-lib" };

  GtkWidget *dialog;
  gchar     *msg = g_strdup_vprintf (fmt, ap);
  va_end (ap);

  switch (type)
  {
  case GTK_MESSAGE_WARNING:
    if (primary)
      g_printerr (_("%s: warning: %s: %s\n"), ptitle[src], primary, msg);
    else
      g_printerr (_("%s: warning: %s\n"), ptitle[src], msg);
    break;
  case GTK_MESSAGE_ERROR:
    if (primary)
      g_printerr (_("%s: error: %s: %s\n"), ptitle[src], primary, msg);
    else
      g_printerr (_("%s: error: %s\n"), ptitle[src], msg);
    break;
  default:;
  }

  gdk_threads_enter ();

  dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_DESTROY_WITH_PARENT |
					   (modal ? GTK_DIALOG_MODAL : 0), type,
				   GTK_BUTTONS_CLOSE, "%s", primary ? : msg);
  if (primary)
    gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
						"%s", msg);
  gtk_window_set_title (GTK_WINDOW (dialog), gettext (wtitle[src][type]));
  gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);

  g_signal_connect (G_OBJECT (dialog), "response",
		    G_CALLBACK (response_cb), modal ? response_cb : NULL);
  g_object_set_data (G_OBJECT (dialog), "msg", msg);
  window_show (dialog, NULL);

  if (modal)
  {
    gtk_dialog_run (GTK_DIALOG (dialog));
    gtk_widget_destroy (dialog);
  }

  gdk_threads_leave ();
}


void display_error_modal (gxine_msg_source src, const gchar *title,
			  const gchar *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  v_display_message (src, TRUE, title, GTK_MESSAGE_ERROR, fmt, ap);
}

void display_error (gxine_msg_source src, const gchar *title,
		    const gchar *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  v_display_message (src, FALSE, title, GTK_MESSAGE_ERROR, fmt, ap);
}

void display_warning (gxine_msg_source src, const gchar *title,
		      const gchar *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  v_display_message (src, FALSE, title, GTK_MESSAGE_WARNING, fmt, ap);
}

void display_info (gxine_msg_source src, const gchar *title,
		   const gchar *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  v_display_message (src, FALSE, title, GTK_MESSAGE_INFO, fmt, ap);
}

void int_to_timestring (int int_time, char *string_time, gint length)
{
  int sign = int_time < 0;
  int_time = abs (int_time);
  if (int_time % 1000)
    snprintf (string_time, length, "%s%02d:%02d:%06.3lf",
	      sign ? "-" : "",
	      int_time / 3600000,
	      (int_time / 60000) % 60,
	      (int_time % 60000) / 1000.0);
  else
    snprintf (string_time, length, "%s%02d:%02d:%02.0lf",
	      sign ? "-" : "",
	      int_time / 3600000,
	      (int_time / 60000) % 60,
	      (int_time % 60000) / 1000.0);
}

int parse_timestring (const char *string_time)
{
  int hours = 0, minutes = 0;
  double seconds = 0;

  if (sscanf (string_time, "%d:%d:%lf", &hours, &minutes, &seconds) == 3)
    return hours * 3600000 + minutes * 60000 + seconds * 1000;

  if (sscanf (string_time, "%d:%lf", &minutes, &seconds) == 2)
    return minutes * 60000 + seconds * 1000;

  return 0;
}

static char *filepath = NULL;

static void set_start_dir (GtkWidget *dbox)
{
  if (!filepath)
  {
    xine_cfg_entry_t entry;
    if (xine_config_lookup_entry (xine, "media.files.origin_path", &entry)
	&& entry.type == XINE_CONFIG_TYPE_STRING)
      filepath = strdup (entry.str_value);
    else
      filepath = g_get_current_dir ();
  }
  gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dbox), filepath);
}

static void update_start_dir (GtkWidget *dbox)
{
  gchar *dir = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dbox));
  if (dir)
  {
    free (filepath);
    filepath = dir;
  }
}

#if GXINE_GTK_OLDER_THAN(2,8,0)
WEAK (void, gtk_file_chooser_set_do_overwrite_confirmation);
#endif

gchar *modal_file_dialog (const char *title, gboolean loading, gboolean local,
			  gboolean uri, const gchar *pattern, GtkWidget *extra,
			  GtkWidget *parent)
{
  gchar *ret = NULL;
  GtkWidget *dbox = gtk_file_chooser_dialog_new_with_backend
	(title, NULL,
	 loading ? GTK_FILE_CHOOSER_ACTION_OPEN
		 : GTK_FILE_CHOOSER_ACTION_SAVE,
	 "gnome-vfs",
	 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
	 loading ? GTK_STOCK_OPEN : GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
	 NULL);
  GtkFileChooser *ch = GTK_FILE_CHOOSER (dbox);

  gtk_file_chooser_set_local_only (ch, local);
  gtk_file_chooser_set_select_multiple (ch, FALSE);
  gtk_file_chooser_set_extra_widget (ch, extra);
  if (GTK_HAVE (gtk_file_chooser_set_do_overwrite_confirmation, 2, 8, 0))
    gtk_file_chooser_set_do_overwrite_confirmation (ch, TRUE);

  set_start_dir (dbox);

  window_show (dbox, parent);
  if (gtk_dialog_run (GTK_DIALOG (dbox)) == GTK_RESPONSE_ACCEPT)
  {
    ret = uri ? gtk_file_chooser_get_uri (ch)
	      : gtk_file_chooser_get_filename (ch);
    update_start_dir (dbox);
  }

  gtk_file_chooser_set_extra_widget (ch, NULL);
  gtk_widget_destroy (dbox);
  return ret;
}

GSList *modal_multi_file_dialog (const char *title, gboolean local,
				 const gchar *pattern, GtkWidget *parent)
{
  GSList *ret = NULL;
  GtkWidget *dbox = gtk_file_chooser_dialog_new_with_backend
	(title, NULL, GTK_FILE_CHOOSER_ACTION_OPEN, "gnome-vfs",
	 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
	 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
	 NULL);

  gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (dbox), local);
  gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dbox), TRUE);
  set_start_dir (dbox);

  window_show (dbox, parent);
  if (gtk_dialog_run (GTK_DIALOG (dbox)) == GTK_RESPONSE_ACCEPT)
  {
    ret = gtk_file_chooser_get_uris (GTK_FILE_CHOOSER (dbox));
    update_start_dir (dbox);
  }

  gtk_widget_destroy (dbox);
  return ret;
}

void get_mrl_from_filesystem (GtkWindow *dbox, GtkEntry *entry)
{
  gchar *file = modal_file_dialog (_("Select file for MRL"), TRUE, TRUE, TRUE,
				   NULL, NULL, (GtkWidget *)dbox);
  if (file)
  {
    gtk_widget_grab_focus (GTK_WIDGET (entry));

    const char *suffix = gtk_entry_get_text (entry);
    if (suffix)
    {
      while (isspace (*suffix))
	++suffix;
      suffix = (*suffix && *suffix != '/') ? strchr (suffix, '#') : NULL;
    }

    char *start = g_strconcat (file, suffix, NULL);
    gtk_entry_set_text (entry, start);
    gtk_editable_set_position (GTK_EDITABLE (entry), 0);

    free (start);
    free (file);
  }
}

static gboolean close_cb (GtkWidget *widget, gpointer data)
{
  if (data)
    *((gboolean *)data) = FALSE;
  gtk_widget_hide (widget);
  return TRUE;
}

void hide_on_delete (GtkWidget *widget, gboolean *visible)
{
  g_signal_connect (G_OBJECT(widget), "delete-event",
		    G_CALLBACK(close_cb), visible);
}

void utils_init (void)
{
  /* Nothing to do :-) */
}

void add_table_row_items (GtkWidget *table, gint row, gboolean expand,
			  const gchar *labeltext, ...)
{
  /* Pads out last widget to fill remaining space.
   * First widget is expandable horizontally, rest aren't.
   */
  int column = 1;
  GtkWidget *label = gtk_label_new (labeltext);
  GtkAttachOptions xattach = GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		   yattach = (expand ? GTK_EXPAND | GTK_SHRINK : 0) | GTK_FILL;
  va_list ap;

  gtk_table_attach (GTK_TABLE (table), label, 0, 1, row, row + 1, 0, 0, 2, 2);
  gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);

  va_start (ap, labeltext);
  GtkWidget *widget = va_arg (ap, GtkWidget *);
  while (widget)
  {
    GtkWidget *next = va_arg (ap, GtkWidget *);
    gtk_table_attach (GTK_TABLE (table), widget, column,
		      next ? column + 1 : GTK_TABLE (table)->ncols,
		      row, row + 1, xattach, yattach, 2, 2);
    yattach = xattach = GTK_FILL;
    ++column;
    widget = next;
  }
  va_end (ap);
}

char *asreprintf (char **str, const char *fmt, ...)
{
  char *ret;
  va_list ap;

  va_start (ap, fmt);
  ret = g_strdup_vprintf (fmt, ap);
  va_end (ap);
  free (*str);
  return *str = ret;
}

#ifdef EXP_STUFF
char *unique_name (char *base)
{
  static int i = 0;
  return g_strdup_printf ("%s_%d", base, ++i);
}
#endif

void do_pending_events (void)
{
  while (gtk_events_pending ())
    gtk_main_iteration ();
}

const char *get_copyright_notice (void)
{
  static gchar *copyright = NULL;
  if (!copyright)
    copyright = g_strdup_printf (_("© 2002-%d the xine project team"),
				 2006);
  return copyright;
}
